﻿#region Legal notice
/*
 * This file is part of EPPLib.
 * Copyright 2010, 2011 
 * Gianluca Esposito (info@gianlucaesposito.it)
 * ----------------------------------------------------------------------------
 * EPPLib is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 * 
 * EPPLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Nome-Programma; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
#endregion

#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Xml.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Linq;
#endregion

namespace EPPLib.Messages
{
    public class MessageContainer : BaseContainer
    {
        #region Constants
        private const string USERNAME_SUFFIX = "-REG";
        private const int CLIENTTRANSACTIONID_LENGTH = 32;
        private const char CLIENTTRANSACTIONID_PADCHAR = '0';
        #endregion

        #region Enums
        public enum CommandTypes
        {
            Create,
            Update,
            Delete,
            Transfer,
            Check,
            Info,
            Poll,
            Login,
            Logout,
            Hello,
            Undefined
        }
        public enum Operations
        {
            Query,
            Request,
            Acknownledgment,
            TransferRequest,
            TransferCancel,
            TransferApprove,
            TransferReject,
            Undefined
        }
        #endregion

        #region Members
        private CommandTypes _type;
        private Operations _operation;
        private IMessage _command;
        private IMessage _extension;
        private string _clientTransactionID;
        private string _messageID;
        #endregion

        #region Properties
        public CommandTypes Type { get { return _type; } set { _type = value; } }
        public Operations Operation { get { return _operation; } set { _operation = value; } }
        public IMessage Command { get { return _command; } set { _command = value; } }
        public IMessage Extension { get { return _extension; } set { _extension = value; } }
        public string ClientTransactionID { get { return _clientTransactionID; } set { _clientTransactionID = value; } }
        public string MessageID { get { return _messageID; } set { _messageID = value; } }
        #endregion

        #region Constructor(s)
        public MessageContainer()
        {
            _type = CommandTypes.Undefined;
            _operation = Operations.Undefined;
            _command = null;
            _extension = null;
            _clientTransactionID = null;
            _messageID = null;
        }
        #endregion

        #region Public methods
        public new XDocument Serialize()
        {
            XNamespace commandNamespace = ROOT_ELEMENT_NAME_NAMESPACE;

            if (_type == CommandTypes.Logout) _useSchemaNamespace = false;

            XDocument document = base.Serialize();
            if (_command != null)
            {
                XElement typeElement = null;
                switch (_type)
                {
                    case CommandTypes.Create:
                        XElement createElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "create", createElement);
                        break;
                    case CommandTypes.Update:
                        XElement updateElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "update", updateElement);
                        break;
                    case CommandTypes.Delete:
                        XElement deleteElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "delete", deleteElement);
                        break;
                    case CommandTypes.Transfer:
                        XElement transferElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "transfer", transferElement);
                        break;
                    case CommandTypes.Check:
                        XElement checkElement = XElement.Parse( _command.Serialize());
                        typeElement = new XElement(commandNamespace + "check", checkElement);
                        break;
                    case CommandTypes.Info:
                        XElement infoElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "info", infoElement);
                        break;
                    case CommandTypes.Login:
                        XElement loginElement = XElement.Parse(_command.Serialize());
                        typeElement = new XElement(commandNamespace + "login", loginElement);
                        break;
                    case CommandTypes.Logout:
                        typeElement = new XElement(commandNamespace + "logout");
                        break;
                    case CommandTypes.Hello:
                        typeElement = new XElement(commandNamespace + "hello");
                        break;
                    case CommandTypes.Poll:
                        typeElement = new XElement(commandNamespace + "poll");
                        break;
                }
                if (typeElement != null)
                {
                    XAttribute operationAttribute = null;
                    XAttribute messageIDAttribute = null;
                    switch (_operation)
                    {
                        case Operations.Query:
                            operationAttribute = new XAttribute("op", "query");
                            break;
                        case Operations.Request:
                            operationAttribute = new XAttribute("op", "req");
                            break;
                        case Operations.TransferRequest:
                            operationAttribute = new XAttribute("op", "request");
                            break;
                        case Operations.TransferApprove:
                            operationAttribute = new XAttribute("op", "approve");
                            break;
                        case Operations.TransferCancel:
                            operationAttribute = new XAttribute("op", "cancel");
                            break;
                        case Operations.TransferReject:
                            operationAttribute = new XAttribute("op", "reject");
                            break;
                        case Operations.Acknownledgment:
                            operationAttribute = new XAttribute("op", "ack");
                            messageIDAttribute = new XAttribute("msgID", _messageID);
                            break;
                    }
                    if (operationAttribute != null) typeElement.Add(operationAttribute);
                    if (_operation == Operations.Acknownledgment && messageIDAttribute != null) typeElement.Add(messageIDAttribute);

                    XElement commandElement = null;
                    if (_type.Equals(CommandTypes.Hello))
                    {
                        document.Root.Add(typeElement);
                    }
                    else if (_type.Equals(CommandTypes.Login))
                    {
                        commandElement = new XElement(commandNamespace + "command", typeElement.FirstNode);
                        document.Root.Add(commandElement);
                    }
                    else
                    {
                        commandElement = new XElement(commandNamespace + "command", typeElement);
                        document.Root.Add(commandElement);
                    }

                    if (_extension != null)
                    {
                        string serialized = _extension.Serialize();
                        XElement extensionInnerElement = XElement.Parse(serialized);
                        XElement extensionElement = new XElement(commandNamespace + "extension", extensionInnerElement);
                        commandElement.Add(extensionElement);
                    }

                    if (_clientTransactionID != null && commandElement != null)
                    {
                        XElement clientTransactionIDElement = new XElement(commandNamespace + "clTRID", _clientTransactionID);
                        commandElement.Add(clientTransactionIDElement);
                    }
                }


            }
            
            return document;
        }

        public static string ComposeClientTransactionID(string username, int operationCount)
        {
            string idPrefix = username.Replace(USERNAME_SUFFIX, string.Empty);
            string idCounter = operationCount.ToString().PadLeft(CLIENTTRANSACTIONID_LENGTH, CLIENTTRANSACTIONID_PADCHAR);
            return string.Format("{0}-{1}", idPrefix, idCounter);
        }

        public override string ToString()
        {
            XDocument document = Serialize();
            MemoryStream ms = new MemoryStream();
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new UTF8Encoding(false);
            settings.ConformanceLevel = ConformanceLevel.Document;
            settings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(ms, settings))
            {
                document.Save(writer);
            }
            return ToString(ms.ToArray());
        }
        #endregion

        private string ToString(Byte[] bytes)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            String constructedString = encoding.GetString(bytes);
            return (constructedString);
        }
    }
}